---
title: Plugins
description: Learn how to create and use plugins to extend Motia's functionality
---

import { Files, Folder, File } from 'fumadocs-ui/components/files';
import { Cards, Card } from 'fumadocs-ui/components/card';
import { Callout } from 'fumadocs-ui/components/callout';
import { Steps, Step } from 'fumadocs-ui/components/steps';

# Plugins

Plugins are a powerful way to extend Motia's functionality by adding custom features to the workbench, and integrating with external services.

## What are Plugins?

Plugins in Motia allow you to:

<Cards cols={2}>
  <Card title="Custom Workbench Tabs">Add custom tabs to the workbench interface</Card>
  <Card title="Specialized Visualizations">Create rich tooling tailored to your workflows</Card>
  <Card title="Service Integrations">Integrate with external services and APIs</Card>
  <Card title="Core Extensions">Extend Motia's core functionality with new capabilities</Card>
  <Card title="Reusable Modules">Share reusable functionality across projects</Card>
</Cards>

Motia comes with several official plugins like `plugin-logs`, `plugin-endpoint`, `plugin-observability`, and `plugin-states` that demonstrate the power and flexibility of the plugin system.

## Plugin Architecture

### Core Types

Plugins are built using three main TypeScript types:

#### MotiaPlugin

The main plugin configuration returned by your plugin function:

```typescript
type MotiaPlugin = {
  workbench: WorkbenchPlugin[]  // Array of workbench tab configurations
  dirname?: string              // Optional plugin directory
  steps?: string[]              // Optional custom steps
}
```

#### WorkbenchPlugin

Configuration for a workbench tab:

```typescript
type WorkbenchPlugin = {
  packageName: string           // Package registry name (e.g., '@motiadev/plugin-example')
  componentName?: string        // React component name to render
  label?: string                // Tab label text
  labelIcon?: string            // Icon name from lucide-react
  position?: 'bottom' | 'top'   // Tab position in workbench
  cssImports?: string[]         // CSS files to import
  props?: Record<string, any>   // Props passed to component
}
```

#### MotiaPluginContext

Context object provided to your plugin with access to Motia's internal APIs:

```typescript
type MotiaPluginContext = {
  printer: Printer              // Logging utilities
  state: StateAdapter           // State management
  lockedData: LockedData        // Thread-safe data access
  tracerFactory: TracerFactory  // Tracing functionality
  registerApi: (...)            // Register custom API endpoints
}
```

## Creating a Plugin

### Quick Start with CLI

<Steps>
  <Step title="Generate a plugin project">
    The fastest way to create a new plugin is using the Motia CLI:

    ```bash
    pnpm dlx motia create --plugin my-plugin
    ```
  </Step>
  <Step title="Install dependencies and build">
    After creation, set up and verify the build pipeline:

    ```bash
    cd my-plugin
    pnpm install
    pnpm run build
    ```
  </Step>
</Steps>

<Callout type="success" title="What the CLI template includes">
- TypeScript and React configuration
- tsdown for fast, performant builds with React Compiler and Tailwind CSS v4
- Example workbench UI component
- Required dependencies pre-installed
- Ready-to-build project structure
</Callout>

### Manual Setup

If you prefer to set up manually or want to understand the structure, follow these steps:

<Steps>
  <Step title="Create project structure">
    Create a new directory for your plugin with the following structure:

    <Files>
      <Folder name="plugins" defaultOpen>
        <Folder name="plugin-example" defaultOpen>
          <Folder name="src" defaultOpen>
            <Folder name="components">
              <File name="example-page.tsx">Main UI component</File>
            </Folder>
            <File name="index.ts">Package entry point</File>
            <File name="plugin.ts">Plugin definition</File>
            <File name="styles.css">Tailwind styles</File>
          </Folder>
          <File name="package.json">Project metadata</File>
          <File name="tsconfig.json">TypeScript configuration</File>
          <File name="tsdown.config.ts">tsdown build/bundle configuration</File>
          <File name="postcss.config.js">PostCSS configuration</File>
          <File name="README.md">Plugin documentation</File>
        </Folder>
      </Folder>
    </Files>
  </Step>
  <Step title="Configure package.json">
    Create a `package.json` with proper exports for both the main entry and plugin definition:

    ```json
    {
      "name": "@motiadev/plugin-example",
      "version": "0.14.0-beta.164",
      "type": "module",
      "main": "./dist/index.js",
      "types": "./dist/index.d.ts",
      "exports": {
        ".": {
          "development": "./src/index.ts",
          "default": "./dist/index.js"
        },
        "./plugin": {
          "development": "./src/plugin.ts",
          "default": "./dist/plugin.js"
        },
        "./package.json": "./package.json"
      },
      "files": ["dist"],
      "peerDependencies": {
        "@motiadev/core": "latest",
        "@motiadev/ui": "latest"
      },
      "dependencies": {
        "lucide-react": "^0.555.0",
        "react": "^19.2.0"
      },
      "devDependencies": {
        "@rollup/plugin-babel": "^6.1.0",
        "@tailwindcss/postcss": "^4.1.17",
        "rollup-plugin-postcss": "^4.0.2",
        "@types/node": "^24.10.1",
        "@types/react": "^19.2.7",
        "babel-plugin-react-compiler": "^1.0.0",
        "publint": "^0.3.15",
        "tailwindcss": "^4.1.17",
        "tsdown": "^0.16.8",
        "typescript": "^5.9.3",
        "unplugin-unused": "^0.5.6"
      },
      "publishConfig": {
        "exports": {
          ".": "./dist/index.js",
          "./plugin": "./dist/plugin.js",
          "./package.json": "./package.json"
        }
      }
    }
    ```
  </Step>
  <Step title="Define the plugin entry">
    Create `src/plugin.ts` to define your plugin:

    ```typescript
    import type { MotiaPlugin, MotiaPluginContext } from '@motiadev/core'

    export default function plugin(_motia: MotiaPluginContext): MotiaPlugin {
      return {
        workbench: [
          {
            packageName: '@motiadev/plugin-example',
            cssImports: ['@motiadev/plugin-example/dist/styles.css'],
            label: 'Example',
            position: 'bottom',
            componentName: 'ExamplePage',
            labelIcon: 'sparkles',
          },
        ],
      }
    }
    ```
  </Step>
  <Step title="Build the workbench component">
    Create your React component in `src/components/example-page.tsx`:

    ```typescript
    import { Badge, Button } from '@motiadev/ui'
    import { Sparkles } from 'lucide-react'
    import type React from 'react'

    export const ExamplePage: React.FC = () => {
      return (
        <div className="h-full w-full p-6 overflow-auto">
          <div className="max-w-4xl mx-auto space-y-6">
            <div className="flex items-center gap-3">
              <Sparkles className="w-8 h-8 text-primary" />
              <h1 className="text-3xl font-bold">Example Plugin</h1>
              <Badge variant="info">v1.0.0</Badge>
            </div>

            <p className="text-muted-foreground text-lg">
              Welcome to the example plugin! This demonstrates the basic structure 
              and functionality of a Motia plugin.
            </p>

            <div className="p-6 space-y-4">
              <h2 className="text-xl font-semibold">What is this?</h2>
              <p className="text-muted-foreground">
                This is a minimal example plugin that shows how to create custom 
                workbench tabs in Motia. Plugins can extend the Motia workbench 
                with custom functionality, visualizations, and tools.
              </p>

              <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
                <div className="p-4 border rounded-lg">
                  <h3 className="font-semibold mb-2">Easy to Create</h3>
                  <p className="text-sm text-muted-foreground">
                    Build plugins with React, TypeScript, and Tailwind CSS
                  </p>
                </div>
                <div className="p-4 border rounded-lg">
                  <h3 className="font-semibold mb-2">Integrated</h3>
                  <p className="text-sm text-muted-foreground">
                    Seamlessly integrate with Motia's workbench UI
                  </p>
                </div>
                <div className="p-4 border rounded-lg">
                  <h3 className="font-semibold mb-2">Powerful</h3>
                  <p className="text-sm text-muted-foreground">
                    Access Motia's plugin context and APIs
                  </p>
                </div>
              </div>

              <div className="flex gap-2 mt-6">
                <Button variant="default">
                  <Sparkles className="w-4 h-4" />
                  Get Started
                </Button>
                <Button variant="outline">View Documentation</Button>
              </div>
            </div>
          </div>
        </div>
      )
    }
    ```
  </Step>
  <Step title="Export bundle entry points">
    Create `src/index.ts` to export your components:

    ```typescript
    import './styles.css'

    export { ExamplePage } from './components/example-page'
    ```
  </Step>
  <Step title="Add shared styles">
    Create `src/styles.css` to import Motia's UI styles:

    ```css
    @import "@motiadev/ui/globals.css";
    @import "tailwindcss";
    ```
  </Step>
  <Step title="Configure build tooling">
    **tsdown configuration** — Create `tsdown.config.ts`:

    ```typescript
    import pluginBabel from '@rollup/plugin-babel'
    import postcss from 'rollup-plugin-postcss'
    import { defineConfig } from 'tsdown'

    export default defineConfig([
      // Main JavaScript/TypeScript build
      {
        entry: {
          index: './src/index.ts',
          plugin: './src/plugin.ts',
        },
        format: 'esm',
        platform: 'browser',
        external: [/^react($|\/)/, 'react/jsx-runtime'],
        dts: {
          build: true,
        },
        exports: {
          devExports: 'development',
        },
        clean: true,
        publint: true,
        unused: true,
        outDir: 'dist',
        plugins: [
          pluginBabel({
            babelHelpers: 'bundled',
            parserOpts: {
              sourceType: 'module',
              plugins: ['jsx', 'typescript'],
            },
            plugins: ['babel-plugin-react-compiler'],
            extensions: ['.js', '.jsx', '.ts', '.tsx'],
          }),
        ],
      },
      // Separate CSS build
      {
        entry: {
          styles: './src/styles.css',
        },
        format: 'esm',
        platform: 'browser',
        outDir: 'dist',
        clean: false,
        plugins: [
          postcss({
            extract: true,
            minimize: process.env.NODE_ENV === 'prod',
          }),
        ],
      },
    ])
    ```

    **PostCSS configuration** — Create `postcss.config.js`:

    ```javascript
    export default {
      plugins: {
        '@tailwindcss/postcss': {},
      },
    }
    ```

    **TypeScript configuration** — Create `tsconfig.json`:

    ```json
    {
      "compilerOptions": {
        "target": "ES2020",
        "lib": ["ES2020", "DOM", "DOM.Iterable"],
        "module": "ESNext",
        "moduleResolution": "bundler",
        "jsx": "react-jsx",
        "strict": true,
        "declaration": true,
        "declarationMap": true,
        "sourceMap": true
      },
      "include": ["src"],
      "exclude": ["dist", "node_modules"]
    }
    ```
  </Step>
  <Step title="Add scripts and build">
    Add build scripts to your `package.json`:

    ```json
    {
      "scripts": {
        "build": "tsdown",
        "dev": "tsdown --watch",
        "clean": "rm -rf dist"
      }
    }
    ```

    Then run the build:

    ```bash
    pnpm run build
    ```
  </Step>
</Steps>

## Local Plugins

Local plugins provide a simpler alternative to creating full distributable packages when you want to add custom functionality specific to your project. Unlike publishable plugins, local plugins don't require building, packaging, or separate dependencies—they live directly in your project directory.

### When to Use Local Plugins

Local plugins are ideal for:

<Cards cols={2}>
  <Card title="Development & prototyping">Quickly test plugin ideas without the overhead of package setup.</Card>
  <Card title="Project-specific features">Ship custom functionality that only matters for your project.</Card>
  <Card title="Internal tooling">Build dashboards, monitors, or utilities tailored to your team.</Card>
  <Card title="Learning environment">Understand how plugins work before creating a distributable package.</Card>
</Cards>

### The `~/` Package Name Syntax

<Callout type="info" title="Local package resolution with ~/">
The `~/` prefix in `packageName` tells Motia to load components from your local project directory instead of `node_modules`:

```typescript
workbench: [
  {
    packageName: '~/plugins',  // Loads from <project-root>/plugins
    componentName: 'Plugin',
    // ...
  }
]
```

Motia resolves `~/` to your project root, so you can import components without publishing them as registry packages.
</Callout>

### Creating a Local Plugin

<Steps>
  <Step title="Prepare local directories">
    Create a simple structure in your project:

    <Files>
      <Folder name="playground" defaultOpen>
        <Folder name="plugins" defaultOpen>
          <File name="index.tsx">UI component export</File>
          <Folder name="plugin-api-example">
            <File name="api-example.api.step.ts">Optional plugin-specific steps</File>
          </Folder>
        </Folder>
        <File name="motia.config.ts">Plugin registration</File>
      </Folder>
    </Files>
  </Step>
  <Step title="Define your plugin function">
    In `motia.config.ts`, create a plugin function that returns the plugin configuration:

    ```typescript
    import path from 'node:path'
    import { config, type MotiaPlugin, type MotiaPluginContext } from '@motiadev/core'

    function localPluginExample(motia: MotiaPluginContext): MotiaPlugin {
      // Register custom API endpoint
      motia.registerApi(
        {
          method: 'GET',
          path: '/__motia/local-plugin-example',
        },
        async (req, ctx) => {
          return {
            status: 200,
            body: {
              message: 'Hello from Motia Plugin!',
              timestamp: new Date().toISOString(),
              environment: process.env.NODE_ENV || 'development',
              status: 'active',
            },
          }
        },
      )

      return {
        dirname: path.join(__dirname, 'plugins'),
        steps: ['**/*.step.ts', '**/*_step.py'],
        workbench: [
          {
            componentName: 'Plugin',
            packageName: '~/plugins',  // Load from local project
            label: 'Local Plugin Example',
            position: 'top',
            labelIcon: 'toy-brick',
          },
        ],
      }
    }

    export default config({
      plugins: [localPluginExample],
    })
    ```
  </Step>
  <Step title="Render the plugin UI">
    Create `plugins/index.tsx` with your component:

    ```typescript
    import { Badge, Button, cn } from '@motiadev/ui'
    import { AlertCircle, CheckCircle2, Clock, RefreshCw, Server } from 'lucide-react'
    import { useCallback, useEffect, useState } from 'react'

    interface PluginData {
      message: string
      timestamp: string
      environment: string
      status: 'active' | 'inactive'
    }

    export const Plugin = () => {
      const [data, setData] = useState<PluginData | null>(null)
      const [isLoading, setIsLoading] = useState(true)

      const fetchData = useCallback(async () => {
        setIsLoading(true)
        try {
          const response = await fetch('/__motia/local-plugin-example')
          const result = await response.json()
          setData(result)
        } catch (err) {
          console.error('Failed to fetch data:', err)
        } finally {
          setIsLoading(false)
        }
      }, [])

      useEffect(() => {
        fetchData()
      }, [fetchData])

      return (
        <div className="h-full flex flex-col p-4 gap-4">
          <div className="flex items-center justify-between border-b pb-4">
            <div className="flex items-center gap-3">
              <Server className="w-5 h-5 text-accent-1000" />
              <h1 className="text-xl font-semibold">Local Plugin Example</h1>
            </div>
            <Button onClick={fetchData} disabled={isLoading}>
              <RefreshCw className={cn('w-4 h-4', isLoading && 'animate-spin')} />
              Refresh
            </Button>
          </div>
          
          {data && (
            <div className="space-y-4">
              <div className="p-4 rounded-lg border bg-card">
                <Badge variant={data.status === 'active' ? 'default' : 'secondary'}>
                  {data.status}
                </Badge>
                <p className="text-2xl font-semibold mt-2">{data.message}</p>
              </div>
            </div>
          )}
        </div>
      )
    }
    ```
  </Step>
</Steps>

### Including Custom Steps

<Callout type="default" title="Include custom steps">
Local plugins can also include custom steps by specifying a `dirname` and `steps` pattern:

```typescript
return {
  dirname: path.join(__dirname, 'plugins'),
  steps: ['**/*.step.ts', '**/*_step.py'],
  workbench: [/* ... */],
}
```

This loads API routes, event handlers, and other step types directly from the plugin directory.

**Note:** Plugin steps follow the same discovery rules as regular steps - they can be organized in subdirectories and will be discovered recursively. The `dirname` specifies the root directory to scan, and steps can be nested at any depth within it.
</Callout>

### Best Practices for Local Plugins

<Cards cols={2}>
  <Card title="Keep components simple">Use Motia UI components directly without extra build tooling.</Card>
  <Card title="Use TypeScript">Leverage type safety and IDE support without extra declaration files.</Card>
  <Card title="Organize by feature">Group related components and steps inside meaningful folders.</Card>
  <Card title="Reuse dependencies">Rely on packages already present in your project workspace.</Card>
  <Card title="Document your APIs">Add clear comments for every custom endpoint and interface.</Card>
</Cards>

### When to Migrate to NPM Plugin

<Callout type="warning" title="When to publish as a distributable package">
- You want to share it across multiple projects
- It provides general-purpose functionality
- You need versioning and dependency management
- The plugin is stable and well-tested
</Callout>

## Using Plugins

### Installing a Plugin

<Steps>
  <Step title="Install the plugin package">

    ```bash
    pnpm add @motiadev/plugin-example
    ```
  </Step>
  <Step title="Register it in motia.config.ts">

    ```typescript
    import examplePlugin from '@motiadev/plugin-example/plugin'

    export default {
      plugins: [examplePlugin],
    }
    ```
  </Step>
</Steps>

### Configuring Multiple Plugins

<Callout type="default" title="Configure multiple plugins">

```typescript
import logsPlugin from '@motiadev/plugin-logs/plugin'
import endpointPlugin from '@motiadev/plugin-endpoint/plugin'
import examplePlugin from '@motiadev/plugin-example/plugin'

export default {
  plugins: [
    logsPlugin,
    endpointPlugin,
    examplePlugin,
  ],
}
```

</Callout>
### Registering Custom APIs

<Callout type="info" title="Register custom APIs">
Use the `registerApi` method from the plugin context:

```typescript
import { MotiaPlugin } from './app-config-types'
export default function plugin(motia: MotiaPluginContext): MotiaPlugin {
  // Register a custom API endpoint
  motia.registerApi(
    {
      method: 'GET',
      path: '/api/my-endpoint',
    },
    async (req, res) => {
      return res.json({ message: 'Hello from plugin!' })
    }
  )

  return {
    workbench: [/* ... */],
  }
}
```
</Callout>

## Example Plugin

<Callout type="success" title="Example plugin reference">
A complete minimal example plugin lives at `plugins/plugin-example` in the Motia repository. It demonstrates:

- Basic plugin structure
- Workbench tab integration
- UI component creation
- Build configuration
- TypeScript setup

Use it as a starting point for your own plugins.
</Callout>

## Troubleshooting

<Callout type="warning" title="Plugin not showing in workbench">
- Check that the plugin is imported in `motia.config.ts`
- Verify the `componentName` matches your exported component
- Ensure the plugin is built (`pnpm run build`)
- Check browser console for errors
- Ensure tsdown and Babel plugins are installed
</Callout>

<Callout type="warning" title="Styles not loading">
- Verify CSS is imported in `src/index.ts`
- Check that the CSS build is configured in `tsdown.config.ts`
- Ensure TailwindCSS is properly configured in `postcss.config.js`
- Confirm that `cssImports` is defined in `src/plugin.ts` with the correct path (e.g., `['@motiadev/plugin-example/dist/styles.css']`)
</Callout>

<Callout type="default" title="Resolving type errors">
- Make sure `@motiadev/core` and `@motiadev/ui` are listed in `peerDependencies`
- Run `pnpm install` so TypeScript picks up the types
- Confirm `declaration: true` is set in `tsconfig.json`
- Check external dependencies are correctly configured in tsdown
</Callout>

## Publishing and Contributing Plugins

Want to share your plugin with the Motia community?

<Callout type="info" title="Publish to NPM and contribute">
If you've created a plugin and want to share it with the community:

1. **Publish to NPM** - Make your plugin available for everyone to install
2. **Add to awesome-plugins** - Submit your plugin to our curated list

For complete instructions on creating, publishing, and contributing plugins, see the **[Plugin Contributing Guide](https://github.com/MotiaDev/awesome-plugins/blob/main/CONTRIBUTING.md)**.

The guide includes:
- Step-by-step NPM publishing workflow
- Best practices for plugin development
- Submission format and guidelines
- Troubleshooting common issues
</Callout>

## Next Steps

- Explore the [plugin-logs source code](https://github.com/motiadev/motia/tree/main/plugins/plugin-logs) for a complete example
- Check out the [awesome-plugins repository](https://github.com/MotiaDev/awesome-plugins) for community plugins
- Read the [Plugin Contributing Guide](https://github.com/MotiaDev/awesome-plugins/blob/main/CONTRIBUTING.md) to publish your own
